﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework;

namespace StrategyGame
{
    class Unit : IMapObject, IConstructedByCity
	{
		#region Static resources
		//all possible unit textures
		//static - this variable is shared between every instance of Unit
		static private Texture2D InfantryTexture;
		static private Texture2D CavalryTexture;

		static private Texture2D UnitBackgroundTexture;

		//static function - doesn't refer to any Unit in particular, and can be called separately without a Unit object to hand
		//called in Game.LoadContent before any units are loaded
		static public void LoadContent(ContentManager content)
		{
			InfantryTexture = content.Load<Texture2D>("units/infantry_unit");
			CavalryTexture = content.Load<Texture2D>("units/cavalry_unit");

			UnitBackgroundTexture = content.Load<Texture2D>("units/unit_background");
		}
		#endregion

        #region Unit types
        //when a unit is created it has one of these types (more can be added) that determine what graphic and stats it has
		public enum Type
		{
			Infantry,
			Cavalry
		}

		private readonly string TypeName;
		#endregion	

        #region Regiments in this army
        struct Regiment
        {
            public int Size;
            public Type Type;

            public Regiment(int size, Type type)
            {
                Size = size;
                Type = type;
            }
        }

        List<Regiment> Regiments = new List<Regiment>();

        /// <summary>
        /// Count the total number of troops in all regiments in this unit
        /// </summary>
        /// <returns>Total unit size</returns>
        int TotalSize()
        {
            int result = 0;

            foreach (Regiment regiment in Regiments)
            {
                result += regiment.Size;
            }

            return result;
        }

        #endregion

        #region Movement & move points

        public bool Move(MoveDirection direction)
		{
			int new_x;
			int new_y;

			//do we actually have any move points remaining? if not, don't do anything
			if (RemainingMovePoints <= 0)
			{
				return false;
			}

			if (direction == MoveDirection.Hold)
			{
				RemainingMovePoints = 0;
				return true;
			}
			else
			{
				//set new_x and new_y to the coordinates of the tile we're trying to move into
				switch (direction)
				{
					case MoveDirection.North:
						new_y = Position.Y - 1;
						new_x = Position.X;
						break;
					case MoveDirection.NorthEast:
						new_y = Position.Y - 1;
						new_x = Position.X + 1;
						break;
					case MoveDirection.East:
						new_y = Position.Y;
						new_x = Position.X + 1;
						break;
					case MoveDirection.SouthEast:
						new_y = Position.Y + 1;
						new_x = Position.X + 1;
						break;
					case MoveDirection.South:
						new_y = Position.Y + 1;
						new_x = Position.X;
						break;
					case MoveDirection.SouthWest:
						new_y = Position.Y + 1;
						new_x = Position.X - 1;
						break;
					case MoveDirection.West:
						new_y = Position.Y;
						new_x = Position.X - 1;
						break;
					case MoveDirection.NorthWest:
						new_y = Position.Y - 1;
						new_x = Position.X - 1;
						break;
					default:
						//this is an error - the move direction is invalid! just do nothing
						new_x = 0;
						new_y = 0;
						break;
				}

				TileMap.TileType destination_tile = Map.LookupTile(new_x, new_y);

				//if the tile we're moving into is ocean, return false (move failed), otherwise move and return true
				if (destination_tile == TileMap.TileType.Ocean)
					return false;
				else
				{
					//actually move to the new position
					Position = new Point(new_x, new_y);

					//remove one move point
					RemainingMovePoints -= 1;

					//calculate new visibility
					//this is actually kind of inefficient, but this is a)C# and b)a turn based game so it can probably be left to slide...
					GetMyNation().CalculateVisibleTiles();

					return true;
				}
			}
		}
		
		public enum MoveDirection
		{
			Hold, //don't move
			North,
			NorthEast,
			East,
			SouthEast,
			South,
			SouthWest,
			West,
			NorthWest
		};

		private readonly int MaxMovePoints; //readonly so it can't be changed once it's set
		private int RemainingMovePoints;

		public int GetMaxMovePoints()
		{
			return MaxMovePoints;
		}
		public int GetRemainingMovePoints()
		{
			return RemainingMovePoints;
		}
		
		#endregion

		#region Combat

		private int Strength; //number of guys

        public int GetStrength()
        {
            return Strength;
        }

		public void Attack(Unit other_unit)
		{
			//ok how does this work
		}
		#endregion

		#region Implementation of IMapObject

		public Point Position { get; private set; }
		public string Name { get { return TypeName; } }

		public bool BlocksUnitMovement { get { return true; } }
        public bool BlocksConstruction { get { return false; } }

		#endregion

		#region Implementation of IConstructedByCity
		
		public Texture2D ConstructionProjectIcon { get { return Texture; } }
		public string ConstructionProjectName { get { return TypeName; } }
		
		#endregion

		#region Implementation of IConstructionProject (ConstructionProject subclass)
		public class ConstructionProject : IConstructionProject
		{
			public int ManpowerRequired
			{
				get
				{
					switch (Type)
					{
						case Unit.Type.Cavalry: return 1000;
						case Unit.Type.Infantry: return 500;
						default: return 0;
					}
				}
			}

			public int PopulationConsumed
			{
				get
				{
					switch (Type)
					{
						case Unit.Type.Cavalry: return 10;
						case Unit.Type.Infantry: return 20;
						default: return 0;
					}
				}
			}

			public string[] GetProblemsBuildingThis(City at_city)
			{
				if (PopulationConsumed > at_city.Population)
					return new string[] { "Not enough population" };

				TileMap map = at_city.Map;

				switch (Type)
				{
					default:
						//check for any adjacent land tile
						//if (!at_city.Map.GetRandomAdjacentTileOfTypes(at_city.Position, true, false, new TileMap.TileType[] { TileMap.TileType.Grassland, TileMap.TileType.Desert }).HasValue)
						if (!map.RandomTileFromList(at_city.GetTilesAdjacentToBuildings(), obj => !obj.BlocksConstruction, tile => tile != TileMap.TileType.Ocean, at_city.GetMapBoundsRect()).HasValue)
							return new string[] { "No free empty tiles" };
						else
							break;
				}

				//if we reach this line without returning, there's no problems! so return null
				return null;
			}

			public Texture2D ProjectIcon { get { return TypeToTexture(Type); } }
			public string ProjectName { get { return Type.ToString(); } }

			public string[] InfoText
			{
				get
				{
					List<string> result = new List<string>();

					switch (Type)
					{
						case Unit.Type.Cavalry:
							result.Add("Fast-moving attack troops");
							result.Add("armed with polearms and");
							result.Add("shortbows.");

							result.Add(String.Empty);

							result.Add("Move: 3/turn");
							break;

						case Unit.Type.Infantry:
							result.Add("Foot soldiers armed with");
							result.Add("stone clubs or axes, and");
							result.Add("light armor.");

							result.Add(String.Empty);

							result.Add("Move: 2/turn");
							break;
					}

					int population_consumed = PopulationConsumed;
					if (population_consumed > 0)
						result.Add("Consumes " + population_consumed.ToString() + " population");

					return result.ToArray();
				}
			}

			public void FinishProject(City finished_at_city)
			{
				TileMap map = finished_at_city.Map;
				Point city_pos = finished_at_city.Position;

				Point output_pos = new Point(0, 0);

				switch (Type)
				{
					//land units: try to spawn from any tile in the city (todo)
					default:
						Point? nearby_land = map.RandomTileFromList(finished_at_city.GetTilesAdjacentToBuildings(),
								obj => !obj.BlocksConstruction,
								tile => tile != TileMap.TileType.Ocean,
								finished_at_city.GetMapBoundsRect());

						//this should theoretically never fail because this is also checked by GetProblems...
						if (nearby_land.HasValue)
							output_pos = nearby_land.Value;
						break;
				}

				Unit finished_unit = new Unit(output_pos, Type, map);

				finished_at_city.GetNation().AddUnit(finished_unit);
				finished_at_city.RemovePopulation(PopulationConsumed);
			}

			Type Type;

			public ConstructionProject(Type type)
			{
				Type = type;
			}
		}
		#endregion

		#region Rendering

		//this unit's texture
        private readonly Texture2D Texture;

		public void Draw(SpriteBatch sprites, Color nation_color, Nation pov_nation, int draw_offset_x, int draw_offset_y)
		{
			//draw rectangle is the tile rectangle plus the camera offset
			//todo: gettilerectangle should do the offset for me
			Rectangle draw_rect = Map.GetTileRectangle(Position.X + draw_offset_x, Position.Y + draw_offset_y);

			//always draw the background circle thing in our national colors
			sprites.Draw(UnitBackgroundTexture, draw_rect, nation_color);

			//draw with a different colour depending on how many move points are left
			Color draw_color;

			if (RemainingMovePoints <= 0)
				draw_color = Color.DarkGray; //silhouetted out when out of moves
			else
				draw_color = Color.Black; //normal if mp remain

			//if this is the world map's active unit, draw it with an outline
			Nation mynation = GetMyNation();

			if (mynation != null                        //this is just a sanity check - of course this unit's nation should probably not be null!
				&& pov_nation == mynation               //is this unit's nation the player nation?
				&& this == mynation.ActiveUnit)    //is this also the active unit
			{
				//if this IS the player's active unit...
				//draw it at various offsets behind the main unit graphic in white, for a white outline

				int outline_size = 2;
				Color outline_color = Color.White;

				Rectangle outline_draw_rect;
				outline_draw_rect = draw_rect;
				outline_draw_rect.Y -= outline_size;
				sprites.Draw(Texture, outline_draw_rect, outline_color);

				outline_draw_rect = draw_rect;
				outline_draw_rect.Y += outline_size;
				sprites.Draw(Texture, outline_draw_rect, outline_color);

				outline_draw_rect = draw_rect;
				outline_draw_rect.X -= outline_size;
				sprites.Draw(Texture, outline_draw_rect, outline_color);

				outline_draw_rect = draw_rect;
				outline_draw_rect.X += outline_size;
				sprites.Draw(Texture, outline_draw_rect, outline_color);
			}

			//finally regardless of everything else, draw the actual unit's graphic...
			sprites.Draw(Texture, draw_rect, draw_color);
		}

		static private Texture2D TypeToTexture(Type unittype)
		{
			switch (unittype)
			{
				case Type.Cavalry: return CavalryTexture;
				case Type.Infantry: return InfantryTexture;
				default: return null;
			}
		}

		#endregion

		#region Utility/info functions

		//Units keep a reference to the TileMap they reside on, for convenience
        public TileMap Map { get; private set; }

        //finds the nation this unit belongs to
        public Nation GetMyNation()
        {
            if (Map != null)
            {
                return Map.FindOwnerOfUnit(this);
            }
            else
            {
                return null;
            }
        }

		#endregion

		public void UpdateTurn(int turn_number)
        {
            //refresh movement points back to max
            RemainingMovePoints = MaxMovePoints;
        }        

        public Unit(Point initial_position, Type initial_type, TileMap on_map)
        {
            Position = new Point(initial_position.X, initial_position.Y);

            Map = on_map;
            Map.AddMapObject(this);

            TypeName = initial_type.ToString();

            //decides what to do based on the value of initial_type
            //this is where all the information about different types of units is kept
            //to add new units all we need to do at the moment is add them to the Type enum, then set their properties here
            switch (initial_type)
            {
                case Type.Cavalry:
                    Strength = 100;
                    MaxMovePoints = 3;
                    break; //oddly case statements aren't separated by {}, they are just a list of statements followed by a break; for each case
                case Type.Infantry:
                    Strength = 200;
                    MaxMovePoints = 2;
                    break;
                default:
                    Strength = 0;
                    MaxMovePoints = 0;
                    //the type given is none of the above? this is an error
                    //do nothing for now
                    break;
            }

            Texture = TypeToTexture(initial_type);

            RemainingMovePoints = MaxMovePoints;
        }
    }
}
